/*
Copyright (c) 2025 HeZongLun
NahidaProject is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan
PSL v2.
You may obtain a copy of Mulan PSL v2 at:
		 http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/

#include "NahidaUnitTest.h"
#include <numeric>

NahidaProject::NahidaUnitTest::TestRegistry* NahidaProject::NahidaUnitTest::TestRegistry::instance_ = nullptr;

NahidaProject::NahidaUnitTest::TestRegistry& NahidaProject::NahidaUnitTest::TestRegistry::GetInstance() {
	if (!instance_) {
		instance_ = new TestRegistry();
	}
	return *instance_;
}

void NahidaProject::NahidaUnitTest::TestRegistry::RegisterTest(std::unique_ptr<TestCase> test) {
	tests.push_back(std::move(test));
}

const std::vector<std::unique_ptr<NahidaProject::NahidaUnitTest::TestCase>>& NahidaProject::NahidaUnitTest::TestRegistry::GetTests() const {
	return tests;
}

void NahidaProject::NahidaUnitTest::TestRegistry::Clear() {
	tests.clear();
}

void NahidaProject::NahidaUnitTest::TestRunner::RunAllTests() {
	auto& registry = NahidaProject::NahidaUnitTest::TestRegistry::GetInstance();
	const auto& tests = registry.GetTests();

	std::cout << "Running " << tests.size() << " tests...\n\n";

	for (const auto& test : tests) {
		NahidaProject::NahidaUnitTest::TestInformation info(test->GetName(), test->GetFile(), test->GetLine());

		auto start_time = std::chrono::high_resolution_clock::now();

		try {
			test->Run();
			info.result = NahidaProject::NahidaUnitTest::TestResult::PASSED;
			passedTest++;
		}
		catch (const AssertionException& e) {
			info.result = NahidaProject::NahidaUnitTest::TestResult::FAILED;
			info.message = e.GetMessage() + " at " + e.GetFile() + ":" + std::to_string(e.GetLine());
			failedTest++;
		}
		catch (const std::exception& e) {
			info.result = NahidaProject::NahidaUnitTest::TestResult::FAILED;
			info.message = "Exception: " + std::string(e.what());
			failedTest++;
		}
		catch (...) {
			info.result = NahidaProject::NahidaUnitTest::TestResult::FAILED;
			info.message = "Unknown exception thrown";
			failedTest++;
		}

		auto end_time = std::chrono::high_resolution_clock::now();
		auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
		info.duration_ms = duration.count() / 1000.0;

		results.push_back(info);

		switch (info.result) {
		case TestResult::PASSED:
			std::cout << "[SUCCESS] " << info.name << " (" << info.duration_ms << " ms)\n";
			break;
		case TestResult::FAILED:
			std::cout << "[FAILURE] " << info.name << " - " << info.message << "\n";
			break;
		case TestResult::SKIPPED:
			std::cout << "[SKIPPED] " << info.name << "\n";
			skippedTest++;
			break;
		}
	}
}

void NahidaProject::NahidaUnitTest::Assert::_AssertTrue(bool condition, const std::string & message, const std::source_location & location) {
		if (!condition) {
			std::ostringstream oss;
			oss << "Expected true, but was false";
			if (!message.empty()) {
				oss << " (" << message << ")";
			}
			throw AssertionException(oss.str(), location.file_name(), location.line());
		}
}

 void NahidaProject::NahidaUnitTest::Assert::_AssertFalse(bool condition, const std::string & message, const std::source_location & location) {
		if (condition) {
			std::ostringstream oss;
			oss << "Expected false, but was true";
			if (!message.empty()) {
				oss << " (" << message << ")";
			}
			throw AssertionException(oss.str(), location.file_name(), location.line());
		}
}

void NahidaProject::NahidaUnitTest::Assert::_Fail(const std::string & message, const std::source_location & location) {
		std::ostringstream oss;
		oss << "Test failed";
		if (!message.empty()) {
			oss << ": " << message;
		}
		throw AssertionException(oss.str(), location.file_name(), location.line());
}

void NahidaProject::NahidaUnitTest::Assert::_NoThrow(std::function<void()> func, const std::string& message, const std::source_location& location) {
	try {
		func();
	}
	catch (...) {
		std::ostringstream oss;
		oss << "Expected no exception but exception was thrown";
		if (!message.empty()) {
			oss << " (" << message << ")";
		}
		throw AssertionException(oss.str(), location.file_name(), location.line());
	}
}

void NahidaProject::NahidaUnitTest::TestRunner::PrintResults() const {
	std::cout << "\n" << std::string(50, '=') << "\n";
	std::cout << "Test Results Summary:\n";
	std::cout << std::string(50, '=') << "\n";
	std::cout << "Total: " << results.size() << "\n";
	std::cout << "Passed: " << passedTest << "\n";
	std::cout << "Failed: " << failedTest << "\n";
	std::cout << "Skipped: " << skippedTest << "\n";

	if (failedTest > 0) {
		std::cout << "\nFailed Tests:\n";
		std::cout << std::string(30, '-') << "\n";
		for (const auto& result : results) {
			if (result.result == TestResult::FAILED) {
				std::cout << result.name << " - " << result.message << "\n";
			}
		}
	}

	std::cout << "\nExecution time: " << std::accumulate(results.begin(), results.end(), 0.0, [](double sum, const TestInformation& info) { return sum + info.duration_ms; }) << " ms\n";
}


void NahidaProject::NahidaUnitTest::TestSuite::AddTest(std::unique_ptr<TestCase> test) {
	tests.push_back(std::move(test));
}

const std::vector<std::unique_ptr<NahidaProject::NahidaUnitTest::TestCase>>& NahidaProject::NahidaUnitTest::TestSuite::GetTests() const {
	return tests;
}

const std::string& NahidaProject::NahidaUnitTest::TestSuite::GetName() const { 
	return name; 
}

const std::vector<NahidaProject::NahidaUnitTest::TestInformation>& NahidaProject::NahidaUnitTest::TestRunner::GetResults() const {
	return results;
}

int NahidaProject::NahidaUnitTest::TestRunner::GetPassedCount() const {
	return passedTest;
}

int NahidaProject::NahidaUnitTest::TestRunner::GetFailedCount() const {
	return failedTest;
}

int NahidaProject::NahidaUnitTest::TestRunner::GetSkippedCount() const {
	return skippedTest;
}